/*
 * Created on 6. sep.. 2006
 * Created by Tomas Ekeli
 */
package placing;

import java.io.Serializable;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

import people.Person;
import people.Reserver;

/**
 * 
 * <p>The RoomManager handles the Rooms that Roomy knows about.
 *  All access to Rooms and Places should be routed through this. It has 
 *  methods for adding and removing Rooms and Places, and for assigning 
 *  Reservations these Rooms and Places.</p>
 * 
 * @author Tomas Ekeli
 *
 */
public class RoomManager implements Serializable {
	private static final long serialVersionUID = 1L;
	
	private int lastRoomID;
	private boolean sortedState = true;
	private LinkedList<Room> roomsList;

	public RoomManager() {
		roomsList = new LinkedList<Room>();
	}
	

	/**
	 * Checks whether the RoomManager manages any Rooms.
	 * @return true iff no Rooms are Managed
	 */
	public boolean isEmpty() {

		return roomsList.isEmpty();
	}

	/**
	 * Adds a new, empty Room to be managed. This new Room will have an identifying
	 * number one value higher than the highest so far. The Room will have no
	 * Places.
	 * 
	 * @throws RoomException iff the Room could not be added (ie. there was
	 * already a room with a number one higher than the highest, concurrency
	 * issues?)
	 */
	public Room addRoom() throws RoomException {
		return addRoom(0);
		
	}
	
	
	/**
	 * Adds a new Room to be managed with n Places. This new Room will have an 
	 * identifying number one value higher than the highest so far. The Room 
	 * will have numPlaces number of Places.
	 * 
	 * @param numPlaces The number of Places in the new Room
	 * @throws RoomException iff the number of Places specified was illegal 
	 * (ie. places < 0)
	 */
	public Room addRoom(int numPlaces) throws RoomException {
		if (numPlaces < 0) {
			throw new RoomException("Cannot have a negative number of Places in a Room");
		}
		if (!sortedState) {
			Collections.sort(roomsList);
			if (isEmpty()) {
				lastRoomID = 0;
			}
			else {
				lastRoomID = roomsList.getLast().getID();
			}
		}
		Room addMe = new Room(++lastRoomID);
		for (int i = 0; i < numPlaces; i++) {
			addMe.addPlace();
		}
		
		roomsList.add(addMe);
		return addMe;
		
	}


	/**
	 * Adds the room given to the RoomManager.
	 * 
	 * @param r The Room to start managing
	 * @throws RoomException iff the Room given was already managed.
	 */
	public void addRoom(Room r) throws RoomException {
		sortedState = false;
		if (!roomsList.contains(r)) {
			roomsList.add(r);
			sortedState = false;
		}
		else {
			throw new RoomException("Room " + r.getName() + " already managed.");
		}
	}
	
	/**
	 * Fetches the Room with the given identifying number
	 * @param roomID The identifying number of the Room
	 * @return The Room specified
	 * @throws RoomException iff there is no Room with that number under
	 * management
	 */
	public Room getRoom(int roomID) throws RoomException {
		
		/*
		 * Since the Room is Comparable based on roomnumber we can make a
		 * dummy-room and search with that. 
		 */
		try {
			Room returnMe = roomsList.get(roomsList.indexOf(new Room(roomID)));
			return returnMe;
		}
		catch (IndexOutOfBoundsException e) {
			throw new RoomException("Manager could not find Room " + roomID);
		}
		
	}


	/**
	 * Returns all the Rooms managed.
	 * 
	 * @return A List containing all the managed Rooms
	 */
	public List<Room> getRooms() {
		return new LinkedList<Room>(roomsList);
	}


	/**
	 * Searches through the managed Rooms and returns one with at least
	 * one free Place. Does not check for reservations.
	 * 
	 * @return A room with a Place available
	 * @throws PlaceNotFoundException iff there are no free Places
	 */
	public Room getRoomWithFreePlace() throws PlaceNotFoundException {

		for (Room r : roomsList) {
			try {
				r.getRandomFreePlace();
				return r;
			} catch (PlaceNotFoundException e) {
				// try the next one
			}
			
		}
		throw new PlaceNotFoundException("No free places in managed Rooms");
	}


	/**
	 * Searches through the managed Rooms and returns one with a Place that
	 * is free and available to p.
	 * 
	 * @param p The Person to look for a Room for.
	 * @throws PlaceNotFoundException iff no Room has a Place for p
	 */
	public Room getRoomWithFreePlace(Person p) throws PlaceNotFoundException {
		for (Room r : roomsList) {
			try {
				r.getFreePlace(p);
				return r;
			}
			catch (PlaceNotFoundException e) {
				// try the next one
			}
		}
		throw new PlaceNotFoundException("No available Room for " + p.getName());
	}


	/**
	 * Searches through the managed Rooms and a Place that is free and 
	 * available to p.
	 * 
	 * @param p The Person to look for a Place for.
	 * @throws PlaceNotFoundException iff no Room has a Place for p
	 */
	public Place getFreePlace(Person p) throws PlaceNotFoundException {
		if (!sortedState) {
			Collections.sort(roomsList);
		}
		for (Room r : roomsList) {
			try {
				return r.getFreePlace(p);
			}
			catch (PlaceNotFoundException e) {
				// try the next one
			}
		}
		throw new PlaceNotFoundException("No available Place for " + p.getName());
	}
	
	/**
	 * Searches through the managed Rooms and finds a Place that is free and
	 * available to p, only randomly.
	 * 
	 * @param p The Person to look for a random place for
	 * @return PlaceNotFoundException iff no Room has a Place for p
	 */
	public Place getRandomFreePlace(Person p) throws PlaceNotFoundException {
		Collections.shuffle(roomsList);
		sortedState = false;
		
		for (Room r : roomsList) {
			try {
				return r.getRandomFreePlace(p);
			}
			catch (PlaceNotFoundException e) {
				// try the next one
			}
		}
		throw new PlaceNotFoundException("No available Place for " + p.getName());
	}



	/**
	 * Adds the Reserver to the group with a right to the Room.
	 * @param res The Person with the reservation
	 * @param r The Room to make available
	 * @throws RoomException iff the Room is not managed
	 */
	public void reserveRoom(Reserver res, Room r) throws RoomException {
		if (roomsList.contains(r)) {
			r.reserveRoom(res);
		}
		else {
			throw new RoomException("Room " + r.getName() + " cannot be " 
					+"reserved, Room is not managed.");
		}
		
	}
	

	
	/**
	 * Adds the Reserver to the group with a right to the Place.
	 * @param res The Reserver
	 * @param pl The Place
	 * @throws PlaceNotFoundException iff the Place was not in a Room that is managed
	 */
	public void reservePlace(Reserver res, Place pl) throws PlaceNotFoundException {
		if ( !roomsList.contains(new Room( pl.getRoomID() )) ) {
			throw new PlaceNotFoundException("Cannot reserve place " + pl.getPlaceID()
					+ ", place not in a room under management");
		}
		
		pl.reserve(res);
	}
	


	/**
	 * Adds a reservation to a Place in the Room for the Reserver. If there are
	 * free Places in the Room, one of these will be reserved. If there are no
	 * free Places, one of the assigned places will be reserved (but not freed).
	 * 
	 * @param res The Reserver to reserve for
	 * @param r The Room to reserve a Place in
	 * @return The Place that was reserved for the Person
	 * @throws PlaceNotFoundException iff there are no Places in that Room
	 * @throws RoomException 
	 */
	public Place reserveAnyPlaceInRoom(Reserver res, Room r) throws PlaceNotFoundException, RoomException {
		if (!roomsList.contains(r)) {
			throw new RoomException("Cannot reserve in unknown Room");
		}
		Place anyPlace;
		try {
			anyPlace = r.getRandomFreePlace();
			anyPlace.reserve(res);
		} catch (PlaceNotFoundException e) {
			//is it because there are no Places?
			if (r.isEmpty()) {
				throw new PlaceNotFoundException("No Places to reserve in Room "
						+ r.getName());
			}
			
			//No free places, reserve a random taken one
			List<Place> places = r.getPlaces();
			Collections.shuffle(places);
			anyPlace = places.get(places.size());
			anyPlace.reserve(res);
		}
		return anyPlace;
	}


	
	
	/**
	 * Removes the Room given
	 * @param r The Room to remove from management
	 * @throws RoomException iff r was not known
	 */
	public void removeRoom(Room r) throws RoomException {
		if (!roomsList.remove(r)) {
			throw new RoomException("Could not remove " + r.getName() 
					+ " not a managed Room");
		}
		
	}


	/**
	 * Removes the Room with the given identifying number
	 * @param roomID The number of the Room to remove
	 * @throws RoomException iff no Room with that identifying number was known
	 */
	public void removeRoom(int roomID) throws RoomException {
		removeRoom(new Room(roomID));
	}
	
	protected void printout() {
		if (!sortedState) {
			Collections.sort(roomsList);
		}
		for (Room r : roomsList) {
			if (!sortedState) {
				r.sortPlaces();
			}
			System.out.println(r);
		}
		sortedState = true;
	}

}
